package com.lap.web.starter.core.advice;

import static com.lap.framework.common.enums.SystemCode.*;

import com.lap.context.starter.core.helper.MessageHelper;
import com.lap.framework.common.constant.Header;
import com.lap.framework.common.dto.result.ErrorCode;
import com.lap.framework.common.dto.result.Result;
import com.lap.framework.common.exception.BizException;
import com.lap.framework.common.exception.HttpRemoteException;
import com.lap.framework.common.exception.OptimisticLockingFailureException;
import com.lap.framework.common.exception.SystemException;
import com.lap.framework.common.po.ErrorLogPo;
import com.lap.framework.common.tool.JsonUtil;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.env.Environment;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

/**
 * 异常统一处理，默认自动装载
 *
 * <ol>
 *   <li>getMapping 参数校验异常:{@link MethodArgumentNotValidException}
 *   <li>postMapping 参数校验异常: {@link BindException}
 *   <li>业务处理中断异常: {@link BizException}
 *   <li>控制住异常: {@link NullPointerException} ,该异常需要配置一个错误提示 501=发生空指针
 *   <li>系统异常: {@link Exception} , 该异常需要配置一个错误提示 500=服务处理错误，请联系管理员
 * </ol>
 *
 * @author Shuisheng Lao(劳水生)
 * @version 0.0.1
 */
@Slf4j
@RestControllerAdvice
public record ExceptionAdvice(MessageHelper messageHelder, Environment env) {
  public static final String DETAILS = "details";

  @PostConstruct
  public void init() {
    log.info("Initializer web exception advice.");
  }

  // 处理参数校验异常
  @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
  public Result<Object> handleValidationException(Exception e) {
    BindingResult result = getBindingResult(e);
    String userMessage = buildMessage(result);
    Map<String, Object> metadata = buildFieldMetadata(result);

    logError(VALID_FAILURE, userMessage, metadata);
    return createFailResult(VALID_FAILURE, userMessage);
  }

  // 处理业务异常
  @ExceptionHandler(BizException.class)
  public Result<Object> handleBizException(HttpServletRequest request, BizException ex) {
    ErrorCode errorCode = ex.getErrorCode();
    String i18nMessage = getI18nMessage(errorCode.getMessageKey(), ex.getArgs());

    logError(errorCode, i18nMessage, Map.of("url", request.getRequestURI()));
    return createFailResult(errorCode, i18nMessage);
  }

  // 处理系统异常
  @ExceptionHandler(SystemException.class)
  public Result<Object> handleSystemException(HttpServletRequest request, SystemException ex) {
    String userMessage = getI18nMessage(SYS_FAILURE, ex.getLocalizedMessage());
    logError(
        SYS_FAILURE,
        userMessage,
        Map.of("url", request.getRequestURI(), DETAILS, ex.getClass().getSimpleName()));

    log.error("System error occurred:", ex);
    return createFailResult(SYS_ERROR, userMessage);
  }

  // 业务数据过期
  @ExceptionHandler(OptimisticLockingFailureException.class)
  public Result<Object> handleDataStaleException(
      HttpServletRequest request, OptimisticLockingFailureException ex) {
    String userMessage = getI18nMessage(OPTIMISTIC_LOCK);

    logError(
        OPTIMISTIC_LOCK,
        userMessage,
        Map.of("url", request.getRequestURI(), DETAILS, ex.getClass().getSimpleName()));
    return createFailResult(OPTIMISTIC_LOCK, userMessage);
  }

  // 远程调用
  @ExceptionHandler(HttpRemoteException.class)
  public Result<Object> handleHttpRemoteException(
      HttpServletRequest request, HttpRemoteException ex) {
    String userMessage = ex.getMessage();

    logError(
        SYS_REMOTE_CALL, userMessage, Map.of("url", request.getRequestURI(), DETAILS, userMessage));
    return createFailResult(SYS_REMOTE_CALL, userMessage);
  }

  // 处理系统异常
  @ExceptionHandler({NullPointerException.class, Exception.class})
  public Result<Object> handleGenericException(HttpServletRequest request, Exception e) {
    String userMessage = getI18nMessage(SYS_ERROR);
    logError(
        SYS_ERROR,
        userMessage,
        Map.of("url", request.getRequestURI(), DETAILS, e.getClass().getSimpleName()));

    log.error("System error occurred:", e);
    return createFailResult(SYS_ERROR, userMessage);
  }

  // 处理参数绑定异常
  @ExceptionHandler({HttpMessageConversionException.class, ServletRequestBindingException.class})
  public Result<Object> handleParameterException(HttpServletRequest request, Exception ex) {
    String userMessage = getI18nMessage(REQUEST_BAD);
    String errorMessage = resolveMessage(ex);
    logError(
        REQUEST_BAD, userMessage, Map.of("url", request.getRequestURI(), DETAILS, errorMessage));
    return createFailResult(REQUEST_BAD, userMessage);
  }

  // 处理资源未找到异常
  @ExceptionHandler({
    NoHandlerFoundException.class,
    NoResourceFoundException.class,
    HttpRequestMethodNotSupportedException.class
  })
  public Result<Object> handle404(Exception ex) {
    String userMessage = getI18nMessage(REQUEST_NOT_FOUND);
    // 根据异常类型构造不同元数据
    Map<String, Object> metadata = new LinkedHashMap<>();
    if (ex instanceof NoHandlerFoundException nhe) {
      metadata.put("url", nhe.getRequestURL());
      metadata.put("method", nhe.getHttpMethod());
    } else if (ex instanceof NoResourceFoundException nrf) {
      metadata.put("resourcePath", nrf.getResourcePath());
      metadata.put("httpMethod", nrf.getHttpMethod().name());
    } else if (ex instanceof HttpRequestMethodNotSupportedException r) {
      metadata.put("resourcePath", r.getBody().getTitle());
      metadata.put("httpMethod", r.getMethod());
    }
    logError(REQUEST_NOT_FOUND, userMessage, metadata);
    return createFailResult(REQUEST_NOT_FOUND, userMessage);
  }

  // ------------------ 私有辅助方法 ------------------
  private BindingResult getBindingResult(Exception ex) {
    return ex instanceof MethodArgumentNotValidException m
        ? m.getBindingResult()
        : ((BindException) ex).getBindingResult();
  }

  private String buildMessage(BindingResult result) {
    return result.getFieldErrors().stream()
        .findFirst()
        .map(FieldError::getDefaultMessage)
        .map(msg -> msg + " (" + Objects.requireNonNull(result.getFieldError()).getField() + ")")
        .orElse("Validation failed");
  }

  private Map<String, Object> buildFieldMetadata(BindingResult result) {
    return result.getFieldErrors().stream()
        .collect(
            Collectors.toMap(
                FieldError::getField,
                error -> Optional.ofNullable(error.getDefaultMessage()).orElse("NO_MESSAGE")));
  }

  private Result<Object> createFailResult(ErrorCode code, String message) {
    return Result.fail(code, message);
  }

  private String resolveMessage(Exception ex) {
    return ex instanceof ServletRequestBindingException s
        ? s.getDetailMessageCode()
        : ex.getLocalizedMessage();
  }

  private void logError(ErrorCode code, String userMessage, Map<String, Object> metadata) {
    ErrorLogPo logEntry = buildLogEntry(code, userMessage, metadata);
    log.warn("Exception handled: {}", JsonUtil.toJson(logEntry));
  }

  private ErrorLogPo buildLogEntry(
      ErrorCode code, String userMessage, Map<String, Object> metadata) {
    return ErrorLogPo.builder()
        .service(env.getProperty("spring.application.name", "N/A"))
        .internalCode(code.getInternalCode())
        .httpStatus(code.getExternalCode())
        .rootCause(userMessage)
        .metadata(metadata)
        .requestId(MDC.get(Header.REQUEST_ID))
        .timestamp(LocalDateTime.now())
        .build();
  }

  private String getI18nMessage(ErrorCode code) {
    return getI18nMessage(code.getMessageKey());
  }

  private String getI18nMessage(ErrorCode code, Object... objects) {
    return getI18nMessage(code.getMessageKey(), objects);
  }

  private String getI18nMessage(String messageKey, Object... objects) {
    return messageHelder.getMsg(messageKey, objects);
  }
}
